import tkinter as tk
from tkinter import ttk, messagebox
import threading
from selenium import webdriver
from selenium.webdriver.edge.options import Options
from selenium.webdriver.edge.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, NoSuchElementException
import re
import os
from datetime import datetime
import subprocess
import time
import logging
import socket
import uiautomator2 as u2

# Настройка логирования
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)

# ------------------ Глобальные переменные ------------------ #
successful_attempts = 0
failed_attempts = 0
batch_success_count = 0
processing_thread = None
pause_event = threading.Event()
pause_event.set()
is_processing = False
error_attempt_counter = 0
auto_paused_due_to_wifi = False
log_autoscroll_enabled = True
log_autoscroll_var = None  # будет инициализирована ниже

# Пути к файлам
tokens_file = r"C:\Users\1\Desktop\VS 2022\Получение токенов ВК\tokens.txt"
failed_logins_file = r"C:\Users\1\Desktop\VS 2022\Получение токенов ВК\неуспешные логи.txt"
login_pass_file = r"C:\Users\1\Desktop\VS 2022\Получение токенов ВК\log pass.txt"
driver_path = r"C:\Users\1\Desktop\edgedriver_win64\msedgedriver.exe"
last_limit_file = r"last limit.txt"
hide_browser_state_file = r"hide_browser_state.txt"
last_model_file = r"last_model.txt"
run_scenario_state_file = r"run_scenario_state.txt"

devices_by_model = {}
selected_model_var = None
selected_wifi_var = None
available_wifi_networks = []

# ------------------ Функции для работы с лимитом ------------------ #
def get_limit_value():
    try:
        return int(limit_entry.get())
    except ValueError:
        return 0

def save_last_limit():
    try:
        with open(last_limit_file, "w", encoding="utf-8") as f:
            f.write(limit_entry.get())
    except Exception as e:
        log_message(f"Ошибка при сохранении лимита: {e}", log_text, msg_type="error")

def load_last_limit():
    try:
        with open(last_limit_file, "r", encoding="utf-8") as f:
            return f.read().strip()
    except Exception:
        return "0"

# ------------------ Новая функция разделителя токенов ------------------ #
def add_token_separator():
    """
    Добавляет в файл tokens_file разделитель с текущей датой и временем,
    чтобы отделить вновь получаемые токены от старых.
    """
    try:
        with open(tokens_file, "a", encoding="utf-8") as f:
            now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            separator = f"\n{'='*30} {now} {'='*30}\n"
            f.write(separator)
    except Exception as e:
        log_message(f"Ошибка при добавлении разделителя в файл токенов: {e}", log_text, msg_type="error")

# ------------------ Функции сохранения состояния чекбоксов ------------------ #
def save_hide_browser_state(state: bool):
    try:
        with open(hide_browser_state_file, "w", encoding="utf-8") as f:
            f.write(str(state))
    except Exception as e:
        log_message(f"Ошибка при сохранении состояния чекбокса: {e}", log_text, msg_type="error")

def load_hide_browser_state() -> bool:
    try:
        with open(hide_browser_state_file, "r", encoding="utf-8") as f:
            return f.read().strip().lower() == "true"
    except Exception:
        return False

def hide_browser_state_changed(*args):
    save_hide_browser_state(hide_browser_var.get())

def save_run_scenario_state(state: bool):
    try:
        with open(run_scenario_state_file, "w", encoding="utf-8") as f:
            f.write(str(state))
    except Exception as e:
        log_message(f"Ошибка при сохранении состояния сценария: {e}", log_text, msg_type="error")

def load_run_scenario_state() -> bool:
    try:
        with open(run_scenario_state_file, "r", encoding="utf-8") as f:
            return f.read().strip().lower() == "true"
    except Exception:
        return False

def run_scenario_state_changed(*args):
    save_run_scenario_state(run_scenario_var.get())

def save_last_model(model: str):
    try:
        with open(last_model_file, "w", encoding="utf-8") as f:
            f.write(model)
    except Exception as e:
        log_message(f"Ошибка при сохранении выбранной модели: {e}", log_text, msg_type="error")

def load_last_model() -> str:
    try:
        with open(last_model_file, "r", encoding="utf-8") as f:
            return f.read().strip()
    except Exception:
        return ""

def on_model_selected(event=None):
    model = selected_model_var.get()
    save_last_model(model)

# ------------------ Подключение к сети и Wi-Fi ------------------ #
def is_connected() -> bool:
    """
    Быстрая проверка интернет-подключения путём обращения к 8.8.8.8 (Google DNS).
    """
    try:
        socket.create_connection(("8.8.8.8", 53), timeout=2)
        return True
    except OSError:
        return False

def get_connected_wifi() -> str:
    """
    Возвращает SSID текущей Wi-Fi-сети, к которой подключён ПК (Windows).
    """
    try:
        output = subprocess.check_output("netsh wlan show interfaces", shell=True, encoding="utf-8")
        for line in output.splitlines():
            if "SSID" in line and "BSSID" not in line:
                parts = line.split(":", 1)
                if len(parts) > 1:
                    ssid = parts[1].strip()
                    return ssid if ssid else "Нет подключения"
        return "Нет подключения"
    except Exception as e:
        return f"Ошибка: {e}"

def get_available_networks() -> list:
    """
    Возвращает список доступных Wi‑Fi сетей.
    """
    networks = []
    try:
        output = subprocess.check_output("netsh wlan show networks", shell=True, encoding="utf-8")
        for line in output.splitlines():
            if "SSID" in line and ":" in line:
                parts = line.split(":", 1)
                if len(parts) > 1:
                    ssid = parts[1].strip()
                    if ssid and ssid not in networks:
                        networks.append(ssid)
    except Exception as e:
        log_message(f"Ошибка при получении списка сетей: {e}", log_text, msg_type="error")
    return networks

# ------------------ Сценарии UIAutomator2 ------------------ #
def log_scenario_message(message):
    logging.info(message)

def lower_second_shade_and_toggle_switches(d: u2.Device):
    """
    Пример сценариоавтоматизации через UIAutomator2 (опускание шторки и переключение свитчей).
    """
    try:
        log_scenario_message("Опускаем шторку уведомлений...")
        d.open_quick_settings()
        time.sleep(0.3)  # уменьшили с 0.5 до 0.3
        log_scenario_message("Шторка опущена.")
        switch1 = d.xpath('//*[@resource-id="com.android.systemui:id/quick_tile_layout"]/android.widget.Switch[1]')
        if switch1.wait(timeout=3):  # уменьшили таймаут с 5 до 3
            switch1.click()
            log_scenario_message("Первый свитч нажат.")
            time.sleep(0.3)
            switch1.click()
            log_scenario_message("Первый свитч повторно нажат.")
        else:
            log_scenario_message("Первый свитч не найден.")
        time.sleep(0.3)
        switch2 = d.xpath('//*[@resource-id="com.android.systemui:id/quick_tile_layout"]/android.widget.Switch[2]')
        if switch2.wait(timeout=3):
            switch2.click()
            log_scenario_message("Второй свитч нажат.")
        else:
            log_scenario_message("Второй свитч не найден.")
        log_scenario_message("Поднимаем шторку свайпом...")
        d.swipe(500, 1700, 500, 500, duration=0.3)
        time.sleep(0.3)
        log_scenario_message("Шторка поднята.")
    except Exception as e:
        log_scenario_message(f"Ошибка при выполнении сценария: {e}")

def run_flight_mode_scenario():
    """
    Сценарий переключения режима полёта и точки доступа через ADB + UIAutomator2.
    """
    chosen_model = selected_model_var.get()
    if not chosen_model:
        log_message("Не выбрана модель смартфона для сценария. Сценарий не будет запущен.", log_text, msg_type="error")
        return
    serial = devices_by_model.get(chosen_model)
    if not serial:
        log_message(f"Не найден serial для модели '{chosen_model}'.", log_text, msg_type="error")
        return
    try:
        log_message(f"Запуск сценария (режим полёта, точка доступа) на устройстве '{chosen_model}'.",
                    log_text, msg_type="info")
        d = u2.connect(serial)
        log_message(f"Устройство подключено: {d.device_info}", log_text, msg_type="info")
        subprocess.run([
            "adb", "-s", serial, "shell", "am", "start", "-a", "android.settings.AIRPLANE_MODE_SETTINGS"
        ])
        time.sleep(0.3)  # уменьшили с 0.5 до 0.3
        switch_element = d(resourceId="android:id/switch_widget")
        switch_element.click_exists(timeout=3)  # уменьшили с 5 до 3
        time.sleep(0.3)
        switch_element.click_exists(timeout=3)
        time.sleep(0.5)  # уменьшили с 1 до 0.5
        d.app_start("com.android.settings", ".TetherSettings")
        time.sleep(0.3)
        if d(resourceId="com.android.settings:id/recycler_view").child(index=0).exists(timeout=2):
            d(resourceId="com.android.settings:id/recycler_view").child(index=0).click()
            time.sleep(0.3)
        for _ in range(3):
            d.press("back")
            time.sleep(0.2)
        log_message("Ожидание подключения к Wi-Fi на ПК...", log_text, msg_type="info")
        while not is_connected():
            time.sleep(0.3)  # уменьшили с 0.5 до 0.3
        log_message("Wi-Fi подключен на ПК. Продолжается обработка аккаунтов.", log_text, msg_type="info")
        time.sleep(1)  # уменьшили с 3 до 1
    except Exception as e:
        log_message(f"Ошибка при выполнении сценария смены режима: {e}", log_text, msg_type="error")

# ------------------ Логирование и обновление ------------------ #
def log_message(message, text_widget, msg_type="info"):
    """
    Добавляет сообщение в указанный text_widget (например, log_text) через метод after,
    чтобы избежать проблем при обновлении GUI из другого потока.
    """
    def append_message():
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        full_message = f"[{timestamp}] {message}\n"
        text_widget.config(state=tk.NORMAL)
        if msg_type == "info":
            text_widget.insert(tk.END, full_message, "info")
        elif msg_type == "success":
            text_widget.insert(tk.END, full_message, "success")
        elif msg_type == "error":
            text_widget.insert(tk.END, full_message, "error")
        if text_widget is log_text and log_autoscroll_enabled:
            text_widget.see(tk.END)
        text_widget.config(state=tk.DISABLED)
    text_widget.after(0, append_message)

def append_token(token):
    successful_tokens_text.config(state=tk.NORMAL)
    successful_tokens_text.insert(tk.END, token + "\n")
    successful_tokens_text.config(state=tk.DISABLED)

def append_failed_login(login, password):
    failed_logins_text.config(state=tk.NORMAL)
    failed_logins_text.insert(tk.END, f"{login}:{password}\n")
    failed_logins_text.config(state=tk.DISABLED)

def update_status_labels():
    accounts = read_login_passwords()
    total_accounts = len(accounts)
    remaining = total_accounts - (successful_attempts + failed_attempts)
    success_label.config(text=f"Успешные попытки: {successful_attempts}")
    failure_label.config(text=f"Неуспешные попытки: {failed_attempts}")
    count_label.config(text=f"Всего аккаунтов: {total_accounts}")
    remaining_label.config(text=f"Оставшиеся аккаунты: {remaining}")

# ------------------ Работа с файлами ------------------ #
def save_token_to_file(token, is_error=False):
    """
    Сохраняет строку в файл tokens_file.
    При is_error=True сохраняет с префиксом «Ошибка:».
    """
    try:
        with open(tokens_file, "a", encoding='utf-8') as file:
            if is_error:
                file.write(f"Ошибка: {token}\n")
            else:
                file.write(token + "\n")
        log_message(f"{'Ошибка' if is_error else 'Токен'} сохранён в файле: {tokens_file}", log_text, msg_type="info")
        if not is_error:
            append_token(token)
    except Exception as e:
        log_message(f"Ошибка при сохранении токена: {e}", log_text, msg_type="error")

def save_failed_login(login, password):
    """
    Записывает неуспешный логин:пароль в файл failed_logins_file
    и добавляет в текстовое поле failed_logins_text.
    """
    try:
        with open(failed_logins_file, "a", encoding='utf-8') as file:
            file.write(f"{login}:{password}\n")
        log_message(f"Неуспешный логин сохранён в файле: {failed_logins_file}", log_text, msg_type="info")
        append_failed_login(login, password)
    except Exception as e:
        log_message(f"Ошибка при сохранении неуспешного логина: {e}", log_text, msg_type="error")

def read_login_passwords():
    """
    Считывает логины и пароли из файла login_pass_file,
    возвращает список кортежей (login, password).
    """
    if not os.path.exists(login_pass_file):
        log_message(f"Файл с логинами и паролями не найден: {login_pass_file}", log_text, msg_type="error")
        return []
    with open(login_pass_file, "r", encoding='utf-8') as file:
        accounts = [line.strip().split(":", 1) for line in file if line.strip()]
    return accounts

# ------------------ Основная логика через Selenium ------------------ #
def open_browser(login, password):
    """
    Запускает браузер, проходит авторизацию ВК на https://vkhost.github.io/
    и извлекает access_token, если авторизация успешна.
    """
    global successful_attempts, failed_attempts, batch_success_count
    log_message(f"Открытие браузера для аккаунта: {login}", log_text, msg_type="info")
    options = Options()
    options.add_argument("--inprivate")
    
    if hide_browser_var.get():
        # Запуск в headless-режиме
        options.add_argument("--headless")
        options.add_argument("--disable-gpu")
        log_message("Браузер запускается в скрытом режиме (headless).", log_text, msg_type="info")
    
    service = Service(driver_path)
    driver = webdriver.Edge(service=service, options=options)
    driver.get("https://vkhost.github.io/")
    log_message("Страница https://vkhost.github.io/ открыта.", log_text, msg_type="info")
    
    try:
        button = WebDriverWait(driver, 15).until(
            EC.element_to_be_clickable((By.CSS_SELECTOR, "button.btn[onclick='auth(6121396)']"))
        )
        button.click()
        log_message("Кнопка авторизации нажата.", log_text, msg_type="info")
        WebDriverWait(driver, 15).until(lambda d: len(d.window_handles) > 1)
        driver.switch_to.window(driver.window_handles[-1])
        log_message(f"Переключено на новую вкладку с URL: {driver.current_url}", log_text, msg_type="info")
        WebDriverWait(driver, 15).until(
            lambda d: d.current_url.startswith("https://oauth.vk.com/authorize?")
        )
        log_message(f"Страница авторизации ВКонтакте открыта: {driver.current_url}", log_text, msg_type="info")
        email_field = WebDriverWait(driver, 15).until(
            EC.presence_of_element_located((By.CSS_SELECTOR, "input[type='text'][name='email']"))
        )
        password_field = WebDriverWait(driver, 15).until(
            EC.presence_of_element_located((By.CSS_SELECTOR, "input[type='password'][name='pass']"))
        )
        email_field.send_keys(login)
        log_message("Логин введён.", log_text, msg_type="info")
        password_field.send_keys(password)
        log_message("Пароль введён.", log_text, msg_type="info")
        submit_button = WebDriverWait(driver, 15).until(
            EC.element_to_be_clickable((By.CSS_SELECTOR, "button.flat_button.oauth_button.button_wide[type='submit']"))
        )
        submit_button.click()
        log_message("Кнопка 'Войти' нажата.", log_text, msg_type="info")
        
        # Проверяем, не появилась ли ошибка (box_error, captcha и т. п.)
        try:
            found_error = WebDriverWait(driver, 5).until(
                EC.any_of(
                    EC.presence_of_element_located((By.CSS_SELECTOR, ".box_error")),
                    EC.presence_of_element_located((By.CSS_SELECTOR, ".oauth_captcha"))
                )
            )
            error_class = found_error.get_attribute("class")
            if "box_error" in error_class:
                error_text = "Невалидный логин или пароль."
            elif "oauth_captcha" in error_class:
                error_text = "Найдена капча"
            else:
                error_text = "Неизвестная ошибка"
            failed_attempts += 1
            # Добавляем номер (login) к тексту ошибки
            save_token_to_file(f"{error_text}: {login}", is_error=True)
            save_failed_login(login, password)
            log_message(f"Ошибка: {error_text} Авторизация неуспешна.", log_text, msg_type="error")
            append_token(f"{error_text}: {login}")
            return
        except TimeoutException:
            pass

        # Нажимаем кнопку «Разрешить»
        allow_button = WebDriverWait(driver, 15).until(
            EC.element_to_be_clickable((By.CSS_SELECTOR, "button.flat_button[onclick='return allow(this);']"))
        )
        allow_button.click()
        log_message("Кнопка подтверждения нажата.", log_text, msg_type="info")
        
        # Ожидаем, пока в URL не появится access_token
        WebDriverWait(driver, 15).until(
            lambda d: d.current_url.startswith("https://oauth.vk.com/blank.html#access_token=")
        )
        log_message(f"Страница с токеном загружена: {driver.current_url}", log_text, msg_type="info")
        
        url = driver.current_url
        token_match = re.search(r"access_token=([^&]+)&expires_in", url)
        if token_match:
            token = token_match.group(1)
            save_token_to_file(token)
            successful_attempts += 1
            batch_success_count += 1
            log_message("Токен успешно получен.", log_text, msg_type="success")
            current_limit = get_limit_value()
            # Проверяем, не достигнут ли лимит «подряд идущих» удачных токенов
            if current_limit > 0 and batch_success_count >= current_limit:
                run_flight_mode_scenario()
                batch_success_count = 0
        else:
            failed_attempts += 1
            save_token_to_file(f"Токен не найден в URL: {login}", is_error=True)
            save_failed_login(login, password)
            log_message("Ошибка: Токен не найден в URL.", log_text, msg_type="error")
            append_token(f"Токен не найден в URL: {login}")
    except TimeoutException:
        failed_attempts += 1
        save_token_to_file(f"Превышено время ожидания элемента на странице: {login}", is_error=True)
        save_failed_login(login, password)
        log_message(f"Ошибка: Превышено время ожидания элемента на странице. Текущий URL: {driver.current_url}",
                    log_text, msg_type="error")
        append_token(f"Превышено время ожидания элемента на странице: {login}")
    except NoSuchElementException:
        failed_attempts += 1
        save_token_to_file(f"Элемент не найден на странице: {login}", is_error=True)
        save_failed_login(login, password)
        log_message("Ошибка: Элемент не найден на странице.", log_text, msg_type="error")
        append_token(f"Элемент не найден на странице: {login}")
    except Exception as e:
        failed_attempts += 1
        save_token_to_file(f"{str(e)}: {login}", is_error=True)
        save_failed_login(login, password)
        log_message(f"Неожиданная ошибка: {e}", log_text, msg_type="error")
        append_token(f"Неожиданная ошибка: {login}")
    finally:
        driver.quit()
        log_message("Браузер закрыт.", log_text, msg_type="info")
        update_status_labels()

def process_all_accounts():
    """
    Обрабатывает все аккаунты (логин:пароль) по очереди, останавливается при снятии флага is_processing.
    После завершения обработки всех аккаунтов выполняется сценарий, если он включён.
    """
    global is_processing
    accounts = read_login_passwords()
    for login, password in accounts:
        pause_event.wait()
        if not is_processing:
            break
        log_message(f"Обработка аккаунта: {login}", log_text, msg_type="info")
        open_browser(login, password)
    
    # После завершения обработки всех аккаунтов
    if is_processing and run_scenario_var.get():
        log_message("Запуск сценария после обработки всех аккаунтов.", log_text, msg_type="info")
        run_flight_mode_scenario()
    
    is_processing = False
    start_button.config(state=tk.NORMAL)
    pause_button.config(state=tk.DISABLED)
    resume_button.config(state=tk.DISABLED)
    log_message("Обработка всех аккаунтов завершена.", log_text, msg_type="info")

# ------------------ Управление потоком ------------------ #
def start_browser_thread():
    """
    Запускает поток, в котором обрабатываются все аккаунты.
    Перед этим добавляем разделитель в файл с токенами.
    """
    global is_processing, processing_thread, auto_paused_due_to_wifi
    if is_processing:
        messagebox.showinfo("Информация", "Процесс уже запущен.")
        return

    # Добавляем разделитель с датой и временем в файл tokens.txt,
    # чтобы отделить новые токены от предыдущих
    add_token_separator()

    if selected_wifi_var.get() != get_connected_wifi():
        messagebox.showwarning("Внимание", "Текущая Wi-Fi сеть не совпадает с выбранной.\nЗапуск невозможен.")
        return

    save_last_limit()
    is_processing = True
    pause_event.set()
    auto_paused_due_to_wifi = False

    start_button.config(state=tk.DISABLED)
    pause_button.config(state=tk.NORMAL)
    resume_button.config(state=tk.DISABLED)

    def _run():
        try:
            if run_scenario_var.get():
                scenario_thread = threading.Thread(target=run_flight_mode_scenario, daemon=True)
                scenario_thread.start()
                scenario_thread.join()
            process_all_accounts()
        except Exception as ex:
            log_message(f"Ошибка в основном потоке: {ex}", log_text, msg_type="error")
        finally:
            start_button.config(state=tk.NORMAL)
            pause_button.config(state=tk.DISABLED)
            resume_button.config(state=tk.DISABLED)

    processing_thread = threading.Thread(target=_run, daemon=True)
    processing_thread.start()

def pause_processing():
    """
    Приостанавливает процесс обработки (выставляет флаг pause_event в False).
    """
    global auto_paused_due_to_wifi
    if not is_processing:
        return
    pause_event.clear()
    auto_paused_due_to_wifi = False
    log_message("Процесс приостановлен (вручную).", log_text, msg_type="info")
    pause_button.config(state=tk.DISABLED)
    resume_button.config(state=tk.NORMAL)

def resume_processing():
    """
    Возобновляет процесс обработки (pause_event = True), если сеть соответствует выбранной.
    """
    if not is_processing:
        return
    if selected_wifi_var.get() != get_connected_wifi():
        messagebox.showwarning("Внимание", "Невозможно возобновить: текущая Wi-Fi сеть не совпадает с выбранной.")
        return
    pause_event.set()
    log_message("Процесс восстановлен (вручную).", log_text, msg_type="info")
    pause_button.config(state=tk.NORMAL)
    resume_button.config(state=tk.DISABLED)

# ------------------ Функции копирования ------------------ #
def copy_successful_tokens():
    """
    Копирует все успешно полученные токены из поля successful_tokens_text в буфер обмена.
    """
    content = successful_tokens_text.get("1.0", tk.END).strip()
    if not content:
        messagebox.showinfo("Информация", "Нет токенов для копирования.")
        return
    root.clipboard_clear()
    root.clipboard_append(content)
    log_message("Токены скопированы в буфер обмена.", log_text, msg_type="info")

def copy_failed_logins():
    """
    Копирует все неуспешные логины из поля failed_logins_text в буфер обмена.
    """
    content = failed_logins_text.get("1.0", tk.END).strip()
    if not content:
        messagebox.showinfo("Информация", "Нет неуспешных логинов для копирования.")
        return
    root.clipboard_clear()
    root.clipboard_append(content)
    log_message("Неуспешные логины и пароли скопированы в буфер обмена.", log_text, msg_type="info")

def copy_selection():
    """
    Копирует выделенный пользователем фрагмент текста (при правом клике и выборе в контекстном меню).
    """
    try:
        selected_text = root.focus_get().get(tk.SEL_FIRST, tk.SEL_LAST)
        root.clipboard_clear()
        root.clipboard_append(selected_text)
    except tk.TclError:
        pass

def select_all(text_widget):
    """
    Выделить всё содержимое указанного текстового виджета (например, для последующего копирования).
    """
    text_widget.tag_add(tk.SEL, "1.0", tk.END)
    text_widget.mark_set(tk.INSERT, "1.0")
    text_widget.see(tk.INSERT)

# ------------------ Автопрокрутка ------------------ #
def toggle_log_autoscroll():
    """
    Включает/выключает автопрокрутку лога (log_text) при добавлении новых сообщений.
    """
    global log_autoscroll_enabled
    log_autoscroll_enabled = log_autoscroll_var.get()

# ------------------ Контекстные меню ------------------ #
def show_log_context_menu(event):
    log_context_menu.tk_popup(event.x_root, event.y_root)

def show_text_context_menu(event):
    text_context_menu.tk_popup(event.x_root, event.y_root)

# ------------------ Сканирование ADB ------------------ #
def scan_devices():
    """
    Сканирует adb devices -l, извлекает serial и модель (model:***),
    записывает результат в словарь devices_by_model.
    """
    global devices_by_model
    devices_by_model.clear()
    try:
        result = subprocess.run(["adb", "devices", "-l"], capture_output=True, text=True, check=False)
        output = result.stdout.strip().splitlines()
        for line in output[1:]:
            line = line.strip()
            if not line or "offline" in line or "unauthorized" in line or "unknown" in line:
                continue
            parts = line.split()
            serial = parts[0]
            model = None
            for p in parts:
                if p.startswith("model:"):
                    model = p.split(":", 1)[1]
                    break
            if not model:
                model = "UnknownModel"
            devices_by_model[model] = serial
        log_message(f"Устройства обновлены: {devices_by_model}", log_text, msg_type="info")
    except Exception as e:
        log_message(f"Ошибка при сканировании устройств: {e}", log_text, msg_type="error")

    model_list = list(devices_by_model.keys())
    current_selection = load_last_model()

    def update_gui():
        device_combobox["values"] = model_list
        if current_selection in model_list:
            selected_model_var.set(current_selection)
        elif model_list:
            selected_model_var.set(model_list[0])
        else:
            selected_model_var.set("Нет подключённых устройств")
    root.after(0, update_gui)

def scan_devices_async():
    threading.Thread(target=scan_devices, daemon=True).start()

# ------------------ Обновление списка Wi‑Fi ------------------ #
def refresh_wifi_list():
    """
    Вызывает get_available_networks() в отдельном потоке,
    после чего обновляет список в комбобоксе Wi‑Fi.
    """
    global available_wifi_networks
    available_wifi_networks = get_available_networks()
    current_ssid = get_connected_wifi()

    def update_wifi_combobox():
        wifi_combobox["values"] = available_wifi_networks
        if current_ssid in available_wifi_networks:
            selected_wifi_var.set(current_ssid)
    root.after(0, update_wifi_combobox)

def refresh_wifi_async():
    threading.Thread(target=refresh_wifi_list, daemon=True).start()

# ------------------ Окна для просмотра/очистки ------------------ #
def open_tokens_window():
    """
    Открывает новое окно, в котором отображается содержимое файла tokens_file.
    """
    if hasattr(open_tokens_window, 'window') and open_tokens_window.window.winfo_exists():
        open_tokens_window.window.focus()
        return
    tokens_window = tk.Toplevel(root)
    tokens_window.title("Успешные токены")
    tokens_window.geometry("600x400")
    frame = ttk.Frame(tokens_window, padding="10")
    frame.pack(fill=tk.BOTH, expand=True)
    tokens_text = tk.Text(frame, height=20, width=70, state=tk.DISABLED, wrap=tk.WORD)
    tokens_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
    scrollbar = ttk.Scrollbar(frame, orient=tk.VERTICAL, command=tokens_text.yview)
    scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
    tokens_text.config(yscrollcommand=scrollbar.set)
    
    def load_tokens():
        tokens_text.config(state=tk.NORMAL)
        tokens_text.delete("1.0", tk.END)
        if os.path.exists(tokens_file):
            with open(tokens_file, "r", encoding='utf-8') as file:
                tokens = file.read()
                tokens_text.insert(tk.END, tokens)
        else:
            tokens_text.insert(tk.END, "Файл с токенами не найден.")
        tokens_text.config(state=tk.DISABLED)
        
    def clear_tokens():
        if messagebox.askyesno("Подтверждение", "Вы уверены, что хотите очистить все токены?"):
            try:
                open(tokens_file, "w", encoding='utf-8').close()
                tokens_text.config(state=tk.NORMAL)
                tokens_text.delete("1.0", tk.END)
                tokens_text.config(state=tk.DISABLED)
                log_message("Все токены очищены.", log_text, msg_type="info")
                successful_tokens_text.config(state=tk.NORMAL)
                successful_tokens_text.delete("1.0", tk.END)
                successful_tokens_text.config(state=tk.DISABLED)
                update_status_labels()
            except Exception as e:
                messagebox.showerror("Ошибка", f"Не удалось очистить токены: {e}")
    
    load_tokens()
    button_frame = ttk.Frame(tokens_window)
    button_frame.pack(pady=5)
    clear_button = ttk.Button(button_frame, text="Очистить", command=clear_tokens)
    clear_button.pack()
    open_tokens_window.window = tokens_window

def open_failed_logins_window():
    """
    Открывает новое окно, в котором отображается содержимое файла failed_logins_file
    (неуспешные логины и пароли).
    """
    if hasattr(open_failed_logins_window, 'window') and open_failed_logins_window.window.winfo_exists():
        open_failed_logins_window.window.focus()
        return
    failed_window = tk.Toplevel(root)
    failed_window.title("Неуспешные логины и пароли")
    failed_window.geometry("600x400")
    frame = ttk.Frame(failed_window, padding="10")
    frame.pack(fill=tk.BOTH, expand=True)
    failed_text = tk.Text(frame, height=20, width=70, state=tk.DISABLED, wrap=tk.WORD)
    failed_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
    scrollbar = ttk.Scrollbar(frame, orient=tk.VERTICAL, command=failed_text.yview)
    scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
    failed_text.config(yscrollcommand=scrollbar.set)
    
    def load_failed_logins():
        failed_text.config(state=tk.NORMAL)
        failed_text.delete("1.0", tk.END)
        if os.path.exists(failed_logins_file):
            with open(failed_logins_file, "r", encoding='utf-8') as file:
                failed_logins = file.read()
                failed_text.insert(tk.END, failed_logins)
        else:
            failed_text.insert(tk.END, "Файл с неуспешными логинами не найден.")
        failed_text.config(state=tk.DISABLED)
    
    def clear_failed_logins():
        if messagebox.askyesno("Подтверждение", "Вы уверены, что хотите очистить все неуспешные логины и пароли?"):
            try:
                open(failed_logins_file, "w", encoding='utf-8').close()
                failed_text.config(state=tk.NORMAL)
                failed_text.delete("1.0", tk.END)
                failed_text.config(state=tk.DISABLED)
                log_message("Все неуспешные логины и пароли очищены.", log_text, msg_type="info")
                failed_logins_text.config(state=tk.NORMAL)
                failed_logins_text.delete("1.0", tk.END)
                failed_logins_text.config(state=tk.DISABLED)
                update_status_labels()
            except Exception as e:
                messagebox.showerror("Ошибка", f"Не удалось очистить неуспешные логины: {e}")
    
    load_failed_logins()
    button_frame = ttk.Frame(failed_window)
    button_frame.pack(pady=5)
    clear_button = ttk.Button(button_frame, text="Очистить", command=clear_failed_logins)
    clear_button.pack()
    open_failed_logins_window.window = failed_window

def clear_all_logs():
    """
    Очищает все логи, токены, неуспешные логины и текстовые поля.
    """
    if messagebox.askyesno("Подтверждение", "Вы уверены, что хотите очистить все журналы, токены и неуспешные логины?"):
        try:
            open(tokens_file, "w", encoding='utf-8').close()
            open(failed_logins_file, "w", encoding='utf-8').close()
            log_text.config(state=tk.NORMAL)
            log_text.delete("1.0", tk.END)
            log_text.config(state=tk.DISABLED)
            successful_tokens_text.config(state=tk.NORMAL)
            successful_tokens_text.delete("1.0", tk.END)
            successful_tokens_text.config(state=tk.DISABLED)
            failed_logins_text.config(state=tk.NORMAL)
            failed_logins_text.delete("1.0", tk.END)
            failed_logins_text.config(state=tk.DISABLED)
            global successful_attempts, failed_attempts
            successful_attempts = 0
            failed_attempts = 0
            update_status_labels()
            log_message("Все журналы и данные очищены.", log_text, msg_type="info")
            if hasattr(open_tokens_window, 'window') and open_tokens_window.window.winfo_exists():
                open_tokens_window.window.destroy()
            if hasattr(open_failed_logins_window, 'window') and open_failed_logins_window.window.winfo_exists():
                open_failed_logins_window.window.destroy()
        except Exception as e:
            messagebox.showerror("Ошибка", f"Не удалось очистить журналы: {e}")

def restart_failed_accounts():
    """
    Позволяет заново перепроверить аккаунты, которые попали в неуспешные (failed_logins_text).
    Очищает counters, удаляет содержимое поля и файла с неуспешными,
    и запускает их обработку по тому же принципу, что и основную.
    """
    global error_attempt_counter, successful_attempts, failed_attempts, batch_success_count
    successful_attempts = 0
    failed_attempts = 0
    batch_success_count = 0
    error_attempt_counter = 0
    update_status_labels()
    
    # Сбрасываем успешные токены на экране, чтобы всё начинать "с чистого листа"
    successful_tokens_text.config(state=tk.NORMAL)
    successful_tokens_text.delete("1.0", tk.END)
    successful_tokens_text.config(state=tk.DISABLED)
    
    content = failed_logins_text.get("1.0", tk.END).strip()
    if not content:
        messagebox.showinfo("Информация", "Нет аккаунтов для перезапуска.")
        return

    # Очищаем окно и файл неуспешных
    failed_logins_text.config(state=tk.NORMAL)
    failed_logins_text.delete("1.0", tk.END)
    failed_logins_text.config(state=tk.DISABLED)
    try:
        with open(failed_logins_file, "w", encoding="utf-8") as f:
            f.write("")
    except Exception as e:
        log_message(f"Ошибка при очистке файла неуспешных логинов: {e}", log_text, msg_type="error")
    
    def process_failed():
        lines = content.splitlines()
        for line in lines:
            parts = line.split(":", 1)
            if len(parts) == 2:
                login, password = parts
                log_message(f"Перезапуск аккаунта: {login}", log_text, msg_type="info")
                open_browser(login, password)
        update_status_labels()
    
    threading.Thread(target=process_failed, daemon=True).start()

# ------------------ Авто-проверка Wi-Fi ------------------ #
def update_wifi_status():
    """
    Каждые N миллисекунд (root.after), проверяет текущее подключение Wi‑Fi
    и при несоответствии останавливает/возобновляет процесс.
    """
    global auto_paused_due_to_wifi
    current_wifi = get_connected_wifi()
    wifi_status_label.config(text=f"Подключенный Wi-Fi: {current_wifi}")
    selected_wifi = selected_wifi_var.get()
    
    if selected_wifi and selected_wifi != current_wifi:
        # если сейчас идёт обработка и не на паузе – ставим на паузу
        if is_processing and pause_event.is_set():
            pause_event.clear()
            auto_paused_due_to_wifi = True
            log_message("Процесс приостановлен автоматически (Wi-Fi mismatch).", log_text, msg_type="info")
        # если не обрабатываем, просто блокируем кнопку «Запустить»
        if not is_processing:
            start_button.config(state=tk.DISABLED)
    else:
        # если Wi‑Fi соответствует выбранному
        if not is_processing:
            start_button.config(state=tk.NORMAL)
        else:
            # если ранее была автопауза из-за Wi‑Fi, и сейчас пауза не активна – снимаем паузу
            if auto_paused_due_to_wifi and not pause_event.is_set():
                pause_event.set()
                auto_paused_due_to_wifi = False
                log_message("Процесс возобновлён автоматически (Wi-Fi соответствует).", log_text, msg_type="info")
                time.sleep(0.2)  # небольшая пауза для плавности
    # Интервал проверки увеличен до 2000 мс для снижения нагрузки
    root.after(2000, update_wifi_status)

def on_wifi_selected(event=None):
    """
    Срабатывает при выборе Wi‑Fi в комбобоксе.
    Если выбранный не совпадает с текущим, приостанавливаем/блокируем запуск.
    """
    global auto_paused_due_to_wifi
    selected = selected_wifi_var.get()
    current_wifi = get_connected_wifi()
    if selected and selected != current_wifi:
        if is_processing and pause_event.is_set():
            pause_event.clear()
            auto_paused_due_to_wifi = True
            log_message("Процесс приостановлен автоматически (Wi-Fi mismatch).", log_text, msg_type="info")
        if not is_processing:
            start_button.config(state=tk.DISABLED)
    else:
        if not is_processing:
            start_button.config(state=tk.NORMAL)
        else:
            if auto_paused_due_to_wifi and not pause_event.is_set():
                pause_event.set()
                auto_paused_due_to_wifi = False
                log_message("Процесс возобновлён автоматически (Wi‑Fi выбран совпадает с текущей).",
                            log_text, msg_type="info")
                time.sleep(0.2)

# ------------------ Интерфейс Tkinter ------------------ #
root = tk.Tk()
root.title("Получение токенов ВК")
root.resizable(True, True)
root.geometry("1150x750")

main_frame = ttk.Frame(root, padding="10")
main_frame.pack(fill=tk.BOTH, expand=True)

# ------- Панель управления -------
control_section = ttk.LabelFrame(main_frame, text="Панель управления", padding="5")
control_section.pack(fill=tk.X, pady=5)

control_row1 = ttk.Frame(control_section)
control_row1.pack(fill=tk.X, padx=5, pady=2)

start_button = ttk.Button(control_row1, text="Запустить", command=start_browser_thread)
start_button.pack(side=tk.LEFT, padx=5)

pause_button = ttk.Button(control_row1, text="Приостановить", command=pause_processing, state=tk.DISABLED)
pause_button.pack(side=tk.LEFT, padx=5)

resume_button = ttk.Button(control_row1, text="Восстановить", command=resume_processing, state=tk.DISABLED)
resume_button.pack(side=tk.LEFT, padx=5)

restart_button = ttk.Button(control_row1, text="Перезапустить ошибки", command=restart_failed_accounts)
restart_button.pack(side=tk.LEFT, padx=5)

control_row2 = ttk.Frame(control_section)
control_row2.pack(fill=tk.X, padx=5, pady=2)

hide_browser_var = tk.BooleanVar()
hide_browser_var.set(load_hide_browser_state())
hide_browser_var.trace_add("write", hide_browser_state_changed)
hide_browser_check = ttk.Checkbutton(control_row2, text="Скрыть браузер", variable=hide_browser_var)
hide_browser_check.pack(side=tk.LEFT, padx=5)

run_scenario_var = tk.BooleanVar()
run_scenario_var.set(load_run_scenario_state())
run_scenario_var.trace_add("write", run_scenario_state_changed)
run_scenario_check = ttk.Checkbutton(control_row2, text="Запустить сценарий", variable=run_scenario_var)
run_scenario_check.pack(side=tk.LEFT, padx=5)

update_labels_button = ttk.Button(control_row2, text="Обновить метки", command=update_status_labels)
update_labels_button.pack(side=tk.LEFT, padx=5)

clear_logs_button = ttk.Button(control_row2, text="Очистить все журналы", command=clear_all_logs)
clear_logs_button.pack(side=tk.LEFT, padx=5)

scan_devices_button = ttk.Button(control_row2, text="Сканировать устройства", command=scan_devices_async)
scan_devices_button.pack(side=tk.LEFT, padx=5)

control_row3 = ttk.Frame(control_section)
control_row3.pack(fill=tk.X, padx=5, pady=2)

selected_model_var = tk.StringVar()
device_combobox = ttk.Combobox(control_row3, textvariable=selected_model_var, state="readonly", width=30)
device_combobox.pack(side=tk.LEFT, padx=5)
device_combobox.set("Нет подключённых устройств")
device_combobox.bind("<<ComboboxSelected>>", on_model_selected)

limit_label = ttk.Label(control_row3, text="Лимит токенов:")
limit_label.pack(side=tk.LEFT, padx=5)
limit_entry = ttk.Entry(control_row3, width=5)
limit_entry.pack(side=tk.LEFT)
limit_entry.bind("<FocusOut>", lambda e: save_last_limit())
limit_entry.insert(0, load_last_limit())

control_row4 = ttk.Frame(control_section)
control_row4.pack(fill=tk.X, padx=5, pady=2)

wifi_label = ttk.Label(control_row4, text="Выбрать Wi-Fi:")
wifi_label.pack(side=tk.LEFT, padx=5)

selected_wifi_var = tk.StringVar()
wifi_combobox = ttk.Combobox(control_row4, textvariable=selected_wifi_var, state="readonly", width=30)
wifi_combobox.pack(side=tk.LEFT, padx=5)
wifi_combobox.bind("<<ComboboxSelected>>", on_wifi_selected)

refresh_wifi_button = ttk.Button(control_row4, text="Обновить Wi-Fi", command=refresh_wifi_async)
refresh_wifi_button.pack(side=tk.LEFT, padx=5)

# ------- Статус -------
status_section = ttk.LabelFrame(main_frame, text="Статус", padding="5")
status_section.pack(fill=tk.X, pady=5)

status_frame = ttk.Frame(status_section)
status_frame.pack(fill=tk.X, padx=5, pady=5)

success_label = ttk.Label(status_frame, text="Успешные попытки: 0", foreground="green", cursor="hand2")
success_label.pack(side=tk.LEFT, padx=10)
success_label.bind("<Button-1>", lambda e: copy_successful_tokens())

failure_label = ttk.Label(status_frame, text="Неуспешные попытки: 0", foreground="red", cursor="hand2")
failure_label.pack(side=tk.LEFT, padx=10)
failure_label.bind("<Button-1>", lambda e: copy_failed_logins())

count_label = ttk.Label(status_frame, text="Всего аккаунтов: 0", foreground="blue")
count_label.pack(side=tk.LEFT, padx=10)

remaining_label = ttk.Label(status_frame, text="Оставшиеся аккаунты: 0", foreground="orange")
remaining_label.pack(side=tk.LEFT, padx=10)

wifi_status_label = ttk.Label(status_frame, text="Подключенный Wi-Fi: Неизвестно", foreground="purple")
wifi_status_label.pack(side=tk.LEFT, padx=10)

# ------- Основная область (лог, токены и неуспешные) -------
logs_frame = ttk.Frame(main_frame)
logs_frame.pack(fill=tk.BOTH, expand=True, pady=10)

log_frame = ttk.LabelFrame(logs_frame, text="Журнал действий")
log_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5)
log_text = tk.Text(log_frame, height=20, width=40, state=tk.DISABLED, wrap=tk.WORD)
log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
log_scrollbar = ttk.Scrollbar(log_frame, orient=tk.VERTICAL, command=log_text.yview)
log_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
log_text.config(yscrollcommand=log_scrollbar.set)
log_text.tag_config("info", foreground="black")
log_text.tag_config("success", foreground="green")
log_text.tag_config("error", foreground="red")

tokens_frame = ttk.LabelFrame(logs_frame, text="Успешные токены")
tokens_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5)
successful_tokens_text = tk.Text(tokens_frame, height=20, width=40, state=tk.DISABLED, wrap=tk.WORD)
successful_tokens_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
tokens_scrollbar = ttk.Scrollbar(tokens_frame, orient=tk.VERTICAL, command=successful_tokens_text.yview)
tokens_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
successful_tokens_text.config(yscrollcommand=tokens_scrollbar.set)
successful_tokens_text.tag_config("success", foreground="green")
successful_tokens_text.tag_config("error", foreground="red")

failed_frame = ttk.LabelFrame(main_frame, text="Неуспешные логины и пароли")
failed_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=10)
failed_logins_text = tk.Text(failed_frame, height=10, width=80, state=tk.DISABLED, wrap=tk.WORD)
failed_logins_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
failed_scrollbar = ttk.Scrollbar(failed_frame, orient=tk.VERTICAL, command=failed_logins_text.yview)
failed_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
failed_logins_text.config(yscrollcommand=failed_scrollbar.set)
failed_logins_text.tag_config("error", foreground="red")

# ------- Контекстные меню -------
log_context_menu = tk.Menu(root, tearoff=0)
log_autoscroll_var = tk.BooleanVar(value=True)
log_context_menu.add_command(label="Копировать", command=copy_selection)
log_context_menu.add_command(label="Выбрать всё", command=lambda: select_all(log_text))
log_context_menu.add_separator()
log_context_menu.add_checkbutton(
    label="Автопрокрутка",
    variable=log_autoscroll_var,
    onvalue=True,
    offvalue=False,
    command=toggle_log_autoscroll
)

text_context_menu = tk.Menu(root, tearoff=0)
text_context_menu.add_command(label="Копировать", command=copy_selection)
text_context_menu.add_command(label="Выбрать всё", command=lambda: select_all(root.focus_get()))

log_text.bind("<Button-3>", show_log_context_menu)
successful_tokens_text.bind("<Button-3>", show_text_context_menu)
failed_logins_text.bind("<Button-3>", show_text_context_menu)

# ------- Инициализация -------
log_message("Журнал действий:", log_text, msg_type="info")
update_status_labels()
scan_devices_async()   # Сканируем устройства в отдельном потоке
refresh_wifi_async()   # Обновляем список Wi‑Fi в отдельном потоке
update_wifi_status()   # Запускаем цикл проверки Wi‑Fi (каждые 2000 мс)

root.mainloop()
